---
title: 基础概念
description: 抽象、封装、继承、多态
---
## 四大特性
### 封装(Encapsulation)
封装也叫做信息隐藏或者数据访问保护。类通过暴露有限的访问接口，授权外部仅能通过类提供的方式（或者函数）来访问内部信息或者数据。

对于封装这个特性，需要编程语言本身提供一定的语法机制来支持。这个语法机制就是访问权限控制。


如果对类中属性的访问不做限制，那任何代码都可以访问、修改类中的属性，这样虽然更加灵活，但是过度灵活也意味着不可控，属性可以随意被各种奇葩的方式修改，而且修改逻辑可能散落在代码的各个角落，势必影响代码的可读性、可维护性。



除此之外，类仅仅通过有限方法暴露必要的操作，也能提高类的易用性。

### 抽象(Abstraction)
封装主要讲的是如何隐藏信息、保护数据，而 <RedSpan>抽象讲的是如何隐藏方法的具体实现，让调用者只需要关心方法提供了那些功能，并不需要知道这些功能是如何实现</RedSpan>。


在面向对象编程中，可以借助编程语言提供的接口类或者抽象类这两中语法机制，来实现抽象这一特性。

```java
public interface IPictureStorage {
  void savePicture(Picture picture);
  Image getPicture(String pictureId);
  void deletePicture(String pictureId);
  void modifyMetaInfo(String pictureId, PictureMetaInfo metaInfo);
}

public class PictureStorage implements IPictureStorage {
  // ...省略其他属性...
  @Override
  public void savePicture(Picture picture) { ... }
  @Override
  public Image getPicture(String pictureId) { ... }
  @Override
  public void deletePicture(String pictureId) { ... }
  @Override
  public void modifyMetaInfo(String pictureId, PictureMetaInfo metaInfo) { ... }
}
```

在上面的这段代码中，利用了 Java 中的 interface 接口语法来实现抽象特性。调用者在使用图片存储功能的时候，只需要了解 IPictureStorage 这个接口类暴露了那些方法就可以了，不需要去查看 PictureStorage 类里的具体实现逻辑。

抽象这个特性是非常容易实现的，并不需要非得依靠接口类或者抽象类这些特殊语法机制来支持。单纯的 PictureStorage 类本身就满足抽象特性。

类的方法是通过编程语言中的 “函数”这一语法机制来实现的。通过函数包裹具体的实现逻辑，这本身就是一种抽象。

调用者在使用函数的时候，并不需要去研究函数内部的实现逻辑，只需要通过函数的命名、注释或者文档，就可以直接使用了。



:::tip
定义类方法的时候，要有抽象思维，不要在方法定义中，暴露太多的实现细节，以保证在需要改变方法实现逻辑的时候，不用去修改其定义。
:::

### 继承(Inheritance)
继承用来表示类之间的层次关系（is-a），继承可以分为单继承和多继承。

继承需要编程语言支持，比如 Java 使用 extends 关键字来实现继承，C++ 使用冒号。

有些编程语言只持单继承，不支持多继承，比如 Java、C#，而有些编程语言及支持单继承，也支持多继承，比如 C++；

继承最大的一个好处就是代码复用。假设两个类有一些相同的属性和方法，我们就可以将这些相同的部分，抽取到父类中，让两个子类继承父类。这样两个子类就能复用父类中的代码，避免代码重复写很多遍。
当然也可以使用组合关系解决代码复用问题。


但是，继承层次过深过复杂，就会导致代码可读性、可维护性变差。所以，很多人觉得继承是一种反模式。应该尽量少用、甚至不用。

### 多态(Polymorphism)
多态是指，子类可以替换父类，在实际的代码运行过程中，调用子类的方法实现。

```java
public class DynamicArray {
  private static final int DEFAULT_CAPACITY = 10;
  protected int size = 0;
  protected int capacity = DEFAULT_CAPACITY;
  protected Integer[] elements = new Integer[DEFAULT_CAPACITY];

  public int size() { return this.size; }
  public Integer get(int index) { return elements[index];}
  //...省略n多方法...

  public void add(Integer e) {
    ensureCapacity();
    elements[size++] = e;
  }

  protected void ensureCapacity() {
    //...如果数组满了就扩容...代码省略...
  }
}

public class SortedDynamicArray extends DynamicArray {
  @Override
  public void add(Integer e) {
    ensureCapacity();
    int i;
    for (i = size-1; i>=0; --i) { //保证数组中的数据有序
      if (elements[i] > e) {
        elements[i+1] = elements[i];
      } else {
        break;
      }
    }
    elements[i+1] = e;
    ++size;
  }
}

public class Example {
  public static void test(DynamicArray dynamicArray) {
    dynamicArray.add(5);
    dynamicArray.add(1);
    dynamicArray.add(3);
    for (int i = 0; i < dynamicArray.size(); ++i) {
      System.out.println(dynamicArray.get(i));
    }
  }

  public static void main(String args[]) {
    DynamicArray dynamicArray = new SortedDynamicArray();
    test(dynamicArray); // 打印结果：1、3、5
  }
}
```
多态包括参数多态（不同类型参数多种数据结构）、包含多态（父子类型关系）、过载多态（类似于重载，一个名字不同含义）、强制多态（强制类型转换）四种类型。

多态可以提高扩展性和复用性，是很多设计模式、设计原则、编程技巧的代码实现基础。

## 接口类和抽象类
### 抽象类
* 抽象类不允许被实例化，只能被继承。
* 抽象类可以包含属性和方法。方法包括抽象方法和普通方法
* 子类继承抽象类，必须实现抽象类中的所有抽象方法

抽象类实际上就是类，只不过是一种特殊的类，这种类不能被实例化为对象，只能被子类继承。继承关系是一种 is-a 的关系
### 接口类
* 接口类不能包含属性（也就是成员变量）
* 接口只能声明方法，方法不能包含代码实现。
* 类实现接口的时候，必须实现接口中声明的所有方法。

接口类表示一种 has-a 关系，表示具有某些功能。对于接口，有一个更加形象的叫法，那就是协议（contract）。/

:::tip
在开发时，如果要表示一种 is-a 的关系，并且是为了解决代码复用的问题，就使用抽象类；如果要表示一种 has-a 关系，并且是为了解决抽象而非代码复用的问题，那么就可以使用接口。
:::

从类的继承层次上看，抽象类是一种自下而上的设计思路，先有子类的代码重复，然后再抽象成上层的父类（也就是抽象类）。而接口类正好相反，它是一种自上而下的设计思路。一般都是先设计接口，再去考虑具体的实现。

### 抽象类和接口存在的意义
抽象类是对成员变量和方法的抽象，是一种 is-a 关系，是为了解决代码复用的问题。接口仅仅是对方法的抽象，是一种 has-a 关系，表示具有一组行为特性，是为了解决解耦特性，隔离接口和具体的实现，提高代码的可扩展性。


